package service

import (
	core "ez/apps/core/document"
	"ez/config"
	"fmt"
	"gitee.com/dreamwood/ez-go/ez"
	"gitee.com/dreamwood/ez-go/ss"
	"net/http"
	"strings"
	"sync"
	"time"
)

/** 接口权限的规则说明
接口权限的配置规则
接口权限的数据来源于Role角色，
每个角色都可以配置两种接口权限，放通接口和禁止接口，
禁止接口的优先级大于放通接口，
角色的接口权限修改后，更新各个接口的权限，并将更新后的数据写入redis以便于网关查询

接口权限的检验规则
1.IsPublic 为true，表示接口公开，无需权限控制
2.角色的权限配置，如果角色配置了放通接口，那么该角色可以访问该接口
3.角色的权限配置，如果角色配置了禁止接口，那么该角色不能访问该接口
4.如果角色配置了放通接口和禁止接口，但是禁止接口优先级更高，所以角色不能访问禁止接口



*/

type ApiRule struct {
	Key      string
	Public   bool
	AllowIds []int64
	DenyIds  []int64
}

func (this *ApiRule) Serialize() string {
	return ""
}

var apiRules map[string]*ApiRule
var apiRulesLock sync.Mutex

func init() {
	apiRules = make(map[string]*ApiRule)
	apiRulesLock = sync.Mutex{}
}

func CreateApiRoles() {
	crud := core.NewRoleCrud()
	allRoles, e := crud.FindBy(ss.M{}, nil, 0, 0)
	if e != nil {
		ez.LogToConsole(e.Error())
		return
	}
	data := make(map[string]*ApiRule)
	crudApi := core.NewApiCrud()
	for _, role := range allRoles {
		role.LoadApiDeny()
		role.LoadApiAllow()
		for _, api := range role.ApiAllowKeys {
			if _, ok := data[api]; !ok {
				data[api] = &ApiRule{
					Key:      role.Code,
					Public:   false,
					AllowIds: []int64{},
					DenyIds:  []int64{},
				}
			}
			thisApi, _ := crudApi.FindOneBy(ss.M{"route": api}, nil)
			if thisApi.IsPublic {
				data[api].Public = true
			} else {
				data[api].AllowIds = append(data[api].AllowIds, role.Id)
			}
		}
		for _, api := range role.ApiDenyKeys {
			if _, ok := data[api]; !ok {
				data[api] = &ApiRule{
					Key:      role.Code,
					Public:   false,
					AllowIds: []int64{},
					DenyIds:  []int64{},
				}
			}
			thisApi, _ := crudApi.FindOneBy(ss.M{"route": api}, nil)
			if thisApi.IsPublic {
				data[api].Public = true
			} else {
				data[api].DenyIds = append(data[api].DenyIds, role.Id)
			}
		}
	}
	apiRulesLock.Lock()
	defer apiRulesLock.Unlock()
	apiRules = data
}

func CheckAccess(route, token string) (isAllow bool) {
	if strings.HasPrefix(route, "/admin") {
		return true
	}
	_, _, roles := DecodeUserToken(token)
	if CheckIsIgnored(route) {
		isAllow = true
		return
	}
	apiRule, ok := apiRules[route]
	if !ok {
		ez.LogToConsoleNoTrace(fmt.Sprintf("未配置路由规则:%s", route))
		isAllow = true
		return
	}
	if apiRule.Public {
		ez.LogToConsoleNoTrace(fmt.Sprintf("公共路由:%s", route))
		isAllow = true
		return
	}
	for _, id := range roles {
		for _, denyId := range apiRule.DenyIds {
			if id == denyId {
				ez.LogToConsoleNoTrace(fmt.Sprintf("被阻断:%s，Role=%d", route, id))
				//开发模式完全放行
				isAllow = config.ConfigGateWay.AccessControlDev
				return
			}
		}
	}
	for _, allowId := range apiRule.AllowIds {
		for _, id := range roles {
			if id == allowId {
				ez.LogToConsoleNoTrace(fmt.Sprintf("已放行:%s，Role=%d", route, id))
				isAllow = true
				return
			}
		}
	}
	ez.LogToConsoleNoTrace(fmt.Sprintf("默认策略:%s", route))
	isAllow = config.ConfigGateWay.AccessControlDev
	return
}

func CheckAccessByRequest(request *http.Request) bool {
	token := GetTokenFromRequest(request)
	route := request.URL.Path
	return CheckAccess(route, token)
}

func CheckIsIgnored(url string) bool {
	isIgnore := false
	ignores := config.ConfigGateWay.Ignores
	ignores = append(ignores, "/_server_403")
	ignores = append(ignores, "/_server_404")
	ignores = append(ignores, "/_server_500")
	for _, ignore := range config.ConfigGateWay.Ignores {
		if ignore == url {
			isIgnore = true
		}
	}
	return isIgnore
}

func IsTokenExpire(request *http.Request) int {
	token := GetTokenFromRequest(request)
	_, t, _ := DecodeUserToken(token)
	if t+ez.ConfigApi.TokenExpire < time.Now().Unix() {
		return -1
	}
	if t+ez.ConfigApi.TokenExpire/2 < time.Now().Unix() {
		return 0
	}
	return 1
}
